﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using Might.GraphicsEditor.GraphicsObjects;
using Might.GraphicsEditor.Presenter;

namespace Might.GraphicsEditor.Tools {
    internal enum ResizeHandles {
        TopLeft,
        Top,
        TopRight,
        MiddleLeft,
        Middle,
        MiddleRight,
        BottomLeft,
        Bottom,
        BottomRight
    }

    /// <summary>
    /// Class for manipulation with objects
    /// </summary>
    public partial class Resizer {
        private bool IsMouseCaptured;
        private Point startPoint;
        private ResizeHandles resizeHandle;
        private Rect? rect;
        private Matrix transformMatrix;
        private List<Rect> referenceList;
        internal IconPresenter graphics;

        /// <summary>
        /// Constructor for the Resizer class
        /// </summary>
        /// <param name="graphics">Drawing canvas to be used on</param>
        internal Resizer(IconPresenter graphics) {
            InitializeComponent();

            this.graphics = graphics;
            rect = null;
            topgrid.Visibility = Visibility.Collapsed;
            graphics.SelectionChanged += graphics_SelectionChanged;
            graphics.SelectionMoved += graphics_SelectionMoved;

            #region handlers

            TL.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            TL.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            TL.MouseMove += Resizer_MouseMove;

            TM.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            TM.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            TM.MouseMove += Resizer_MouseMove;
            TMx.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            TMx.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            TMx.MouseMove += Resizer_MouseMove;

            TR.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            TR.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            TR.MouseMove += Resizer_MouseMove;

            ML.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            ML.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            ML.MouseMove += Resizer_MouseMove;
            MLx.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            MLx.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            MLx.MouseMove += Resizer_MouseMove;

            MR.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            MR.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            MR.MouseMove += Resizer_MouseMove;
            MRx.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            MRx.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            MRx.MouseMove += Resizer_MouseMove;

            BL.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            BL.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            BL.MouseMove += Resizer_MouseMove;

            BM.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            BM.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            BM.MouseMove += Resizer_MouseMove;
            BMx.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            BMx.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            BMx.MouseMove += Resizer_MouseMove;

            BR.MouseLeftButtonDown += Resizer_MouseLeftButtonDown;
            BR.MouseLeftButtonUp += Resizer_MouseLeftButtonUp;
            BR.MouseMove += Resizer_MouseMove;

            #endregion
        }

        internal void Resizer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
            // in case of flipping object, get actual handle
            startPoint = e.GetPosition(graphics.canvas);
            var r = (sender as Rectangle);
            if (r == null) {
                throw new NullReferenceException("Resizer Mousedown null sender");
            }

            resizeHandle = (ResizeHandles)Enum.Parse(typeof (ResizeHandles), r.Tag.ToString(), true);

            referenceList = new List<Rect>();
            foreach (GraphicsComponent gc in graphics.SelectedShapes) {
                referenceList.Add(new Rect(gc.ga.Left, gc.ga.Top, gc.ga.Width, gc.ga.Height));
            }

            r.CaptureMouse();

            IsMouseCaptured = true;
            e.Handled = true;
        }

        /// <summary>
        /// Used when moving with one of resize handles.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        internal void Resizer_MouseMove(object sender, MouseEventArgs e) {
            // proved switch podle toho, ktery handle drzim a posun podle toho ramecek
            if (!IsMouseCaptured) {
                return;
            }
            Point point = e.GetPosition(graphics.canvas);
            //if (point.X < 0 || point.X > graphics.canvas.ActualWidth ||
            //    point.Y < 0 || point.Y > graphics.canvas.ActualHeight) {
            //    return;
            //}

            transformMatrix = new Matrix();

            switch (resizeHandle) {
                case ResizeHandles.TopLeft:
                    transformMatrix = transformMatrix.ScaleAt(
                        (rect.Value.Width + (-point.X + startPoint.X)) / rect.Value.Width,
                        (rect.Value.Height + (-point.Y + startPoint.Y)) / rect.Value.Height,
                        rect.Value.Width, rect.Value.Height);
                    break;
                case ResizeHandles.Top:
                    transformMatrix = transformMatrix.ScaleAt(
                        1,
                        (rect.Value.Height + (-point.Y + startPoint.Y)) / rect.Value.Height,
                        rect.Value.Width / 2, rect.Value.Height);
                    break;
                case ResizeHandles.TopRight:
                    transformMatrix = transformMatrix.ScaleAt(
                        (rect.Value.Width + point.X - startPoint.X) / rect.Value.Width,
                        (rect.Value.Height + (-point.Y + startPoint.Y)) / rect.Value.Height,
                        0, rect.Value.Height);
                    break;
                case ResizeHandles.MiddleLeft:
                    transformMatrix = transformMatrix.ScaleAt(
                        (rect.Value.Width + (-point.X + startPoint.X)) / rect.Value.Width,
                        1,
                        rect.Value.Width, rect.Value.Height / 2);
                    break;
                case ResizeHandles.Middle: // rect.Value.Width / 2, rect.Value.Height / 2
                    break;
                case ResizeHandles.MiddleRight:
                    transformMatrix = transformMatrix.ScaleAt(
                        (rect.Value.Width + point.X - startPoint.X) / rect.Value.Width,
                        1,
                        0, rect.Value.Height / 2);
                    break;
                case ResizeHandles.BottomLeft:
                    transformMatrix = transformMatrix.ScaleAt(
                        (rect.Value.Width + (-point.X + startPoint.X)) / rect.Value.Width,
                        (rect.Value.Height + point.Y - startPoint.Y) / rect.Value.Height,
                        rect.Value.Width, 0);
                    break;
                case ResizeHandles.Bottom:
                    transformMatrix = transformMatrix.ScaleAt(
                        1,
                        (rect.Value.Height + point.Y - startPoint.Y) / rect.Value.Height,
                        rect.Value.Width / 2, 0);
                    break;
                case ResizeHandles.BottomRight:
                    transformMatrix = transformMatrix.ScaleAt(
                        (rect.Value.Width + point.X - startPoint.X) / rect.Value.Width,
                        (rect.Value.Height + point.Y - startPoint.Y) / rect.Value.Height,
                        0, 0);
                    break;
                default:
                    break;
            }

            graphics.PreviewTransformSelection(referenceList, rect.Value, transformMatrix);
            UpdateRect();

            // TODO here add some kind of timed propagation of changes, not on every move and not only on MouseUp
            //graphics.TransformSelection(); // when uncommented, changes are propagated immediately
        }
        
        internal void Resizer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
            ((Shape)sender).ReleaseMouseCapture();
            graphics.TransformSelection();
            graphics_SelectionChanged(this, null);

            IsMouseCaptured = false;
            e.Handled = true;
        }

        internal void graphics_SelectionChanged(object sender, EventArgs e) {
            transformMatrix = new Matrix();
            rect = null;

            foreach (GraphicsComponent item in graphics.SelectedShapes) {
                AppendToRect(item);
            }

            UpdateRect();
        }

        private void graphics_SelectionMoved(object sender, EventArgs e) {
            rect = null;// TODO optimize this
            foreach (GraphicsComponent item in graphics.SelectedShapes) {
                AppendToRect(item);
            }
            if (rect.HasValue) {
                CENTER.Width = rect.Value.Width;
                CENTER.Height = rect.Value.Height;

                Canvas.SetLeft(this, rect.Value.Left - 6);
                Canvas.SetTop(this, rect.Value.Top - 6);
            } else {
                topgrid.Visibility = Visibility.Collapsed;
            }
        }

        // This function is to be called after a block of operations, this also updates all the annotation values.
        internal void UpdateRect() {
            topgrid.Visibility = Visibility.Visible;
            if (rect.HasValue) {
                Point topleft = transformMatrix.Transform(new Point());
                Point bottomright = transformMatrix.Transform(new Point(rect.Value.Width, rect.Value.Height));

                CENTER.Width = Math.Abs(bottomright.X - topleft.X);
                CENTER.Height = Math.Abs(bottomright.Y - topleft.Y);
                // in case of flipping object, correct the left and top coordinate
                if (transformMatrix.M11 < 0) {
                    topleft.X = topleft.X - CENTER.Width;
                }
                if (transformMatrix.M22 < 0) {
                    topleft.Y = topleft.Y - CENTER.Height;
                }

                Canvas.SetLeft(this, rect.Value.Left + topleft.X - 6);
                Canvas.SetTop(this, rect.Value.Top + topleft.Y - 6);
            } else {
                topgrid.Visibility = Visibility.Collapsed;
            }
        }

        private void AppendToRect(GraphicsComponent item) {
            var itemBounds = new Rect(
                item.Left,
                item.Top,
                item.Width,
                item.Height);

            if (!rect.HasValue) {
                rect = itemBounds;
            } else {
                itemBounds.Union(rect.Value);
                rect = itemBounds;
            }
        }
    }

    /// <summary>
    /// Class used for storing static extension methods
    /// </summary>
    internal static class MyExtensions {
        internal static Matrix Translate(this Matrix m, double x, double y) {
            m.OffsetX += x;
            m.OffsetY += y;
            return m;
        }

        internal static Matrix ScaleAt(this Matrix m, double x, double y, double cx, double cy) {
            return MultiplyMatrix(m, new Matrix(x, 0.0, 0.0, y, cx - (x * cx), cy - (y * cy)));
        }

        internal static Matrix MultiplyMatrix(Matrix matrix1, Matrix matrix2) {
            return new Matrix(
                (matrix1.M11 * matrix2.M11) + (matrix1.M12 * matrix2.M21),
                (matrix1.M11 * matrix2.M12) + (matrix1.M12 * matrix2.M22),
                (matrix1.M21 * matrix2.M11) + (matrix1.M22 * matrix2.M21),
                (matrix1.M21 * matrix2.M12) + (matrix1.M22 * matrix2.M22),
                ((matrix1.OffsetX * matrix2.M11) + (matrix1.OffsetY * matrix2.M21)) + matrix2.OffsetX,
                ((matrix1.OffsetX * matrix2.M12) + (matrix1.OffsetY * matrix2.M22)) + matrix2.OffsetY);
        }
    }
}